#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"

#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
#include <esp_vfs.h>
#include "esp_http_server.h"

#include "web_server.h"
#include "wifi_ap.h"
#include <cJSON.h>

extern const uint8_t index_html_start[] asm("_binary_index_html_start");
extern const uint8_t index_html_end[]   asm("_binary_index_html_end");
extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start");
extern const unsigned char favicon_ico_end[]   asm("_binary_favicon_ico_end");
#define TAG     "web_server"

/* Max length a file path can have on storage */
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN)

/* Max size of an individual file. Make sure this
 * value is same as that set in upload_script.html */
#define MAX_FILE_SIZE   (200*1024) // 200 KB
#define MAX_FILE_SIZE_STR "200KB"

/* Scratch buffer size */
#define SCRATCH_BUFSIZE  8192

#define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) )

httpd_handle_t server = NULL;
httpd_uri_t root;
httpd_uri_t wifi_config;
typedef struct file_server_data {
    /* Base path of file storage */
    char base_path[ESP_VFS_PATH_MAX + 1];

    /* Scratch buffer for temporary storage during file transfer */
    char scratch[SCRATCH_BUFSIZE];
} file_server_data_t;

/* Send HTTP response with a run-time generated html consisting of
 * a list of all files and folders under the requested path.
 * In case of SPIFFS this returns empty list when path is any
 * string other than '/', since SPIFFS doesn't support directories */
esp_err_t http_sendtext_html(httpd_req_t *req)
{
    /* Get handle to embedded file upload script */

    const size_t upload_script_size = (index_html_end - index_html_start);
    // const char TxBuffer[] = "<h1> SSID1 other WIFI</h1>";
    /* Add file upload form and script which on execution sends a POST request to /upload */
    httpd_resp_send_chunk(req, (const char *)index_html_start, upload_script_size);
    // httpd_resp_send_chunk(req,(const char *)TxBuffer,sizeof(TxBuffer));
    return ESP_OK;
}

uint8_t CharToNum(uint8_t Data)
{
    if(Data >= '0' && Data <= '9') {
        return Data - '0';
    } else if(Data >= 'a' && Data <= 'f') {
        switch (Data)
        {
            case 'a':return 10;
            case 'b':return 11;
            case 'c':return 12;
            case 'd':return 13;
            case 'e':return 14;
            case 'f':return 15;
        default:
            break;
        }
    } else if(Data >= 'A' && Data <= 'F') {
        switch (Data) {
            case 'A':return 10;
            case 'B':return 11;
            case 'C':return 12;
            case 'D':return 13;
            case 'E':return 14;
            case 'F':return 15;
        default:
            break;
        }
    }
    return 0;
}

/* Copies the full path into destination buffer and returns
 * pointer to path (skipping the preceding base path) */
const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
{
    if(!base_path) {
        return nullptr;
    }
    const size_t base_pathlen = strlen(base_path);
    size_t pathlen = strlen(uri);

    const char *quest = strchr(uri, '?');
    if (quest) {
        pathlen = MIN(pathlen, quest - uri);
    }
    const char *hash = strchr(uri, '#');
    if (hash) {
        pathlen = MIN(pathlen, hash - uri);
    }

    if (base_pathlen + pathlen + 1 > destsize) {
        /* Full path string won't fit into destination buffer */
        return NULL;
    }

    /* Construct full path (base + path) */
    strcpy(dest, base_path);
    strlcpy(dest + base_pathlen, uri, pathlen + 1);

    /* Return pointer to path, skipping the base */
    return dest + base_pathlen;
}

static esp_err_t index_html_get_handler(httpd_req_t *req)
{
    const size_t index_html_size = (index_html_end - index_html_start);
    httpd_resp_set_type(req, "text/html");
    httpd_resp_send(req, (const char *)index_html_start, index_html_size);
    return ESP_OK;
}

/* Handler to respond with an icon file embedded in flash.
 * Browsers expect to GET website icon at URI /favicon.ico.
 * This can be overridden by uploading file with same name */
static esp_err_t favicon_get_handler(httpd_req_t *req)
{

    const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start);
    httpd_resp_set_type(req, "image/x-icon");
    httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size);
    return ESP_OK;
}

/* 强制门户访问时连接wifi后的第一次任意GET请求 */
static esp_err_t http_firstget_handler(httpd_req_t *req)
{
    // return http_sendtext_html(req);
    char filepath[FILE_PATH_MAX];
    struct stat file_stat;
    //设置HTTP响应头，允许跨域请求。这里的"*"表示允许所有域进行跨域请求。
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

    const char *filename = get_path_from_uri(filepath, ((file_server_data_t *)req->user_ctx)->base_path,
                                                req->uri, sizeof(filepath));
    if (!filename) {
        ESP_LOGE(TAG, "Filename is too long");
        /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
        return ESP_FAIL;
    }


    if (stat(filepath, &file_stat) == -1) {
        /* If file not present on SPIFFS check if URI
         * corresponds to one of the hardcoded paths */
        if (strcmp(filename, "/") == 0) {
            printf("return index_html!\r\n");
            return index_html_get_handler(req);
        } 
        else if (strcmp(filename, "/favicon.ico") == 0) {
            printf("return icon!\r\n");
            return favicon_get_handler(req);
        }
        ESP_LOGE(TAG, "Failed to stat file : %s", filepath);
        /* Respond with 404 Not Found */
        httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
        return ESP_FAIL;
    }
    return ESP_OK;

}

/* 门户页面发回的，带有要连接的WIFI的名字和密码 */
esp_err_t wifi_config_post_handler(httpd_req_t *req)
{
    int32_t total_len = req->content_len;
    int cur_len = 0;
    char *buf = ((file_server_data_t *)(req->user_ctx))->scratch;

    int received = 0;
    if (total_len >= SCRATCH_BUFSIZE) {
         /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
        return ESP_FAIL;
    }

    while (cur_len < total_len) {
        received = httpd_req_recv(req, buf + cur_len, total_len);
        if (received <= 0) {
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
            return ESP_FAIL;
        }
        cur_len += received;
    }

    buf[total_len] = '\0';
    printf("recived data length is :%ld\n",total_len);
    for (int i = 0; i <total_len ; i++){
        putchar(buf[i]);
    }
    printf("\r\nwifi data recived!\r\n");
    cJSON *json_root = cJSON_Parse(buf);
    char user_id[64], user_code[64];
    memset(user_code, 0, 64);
    memset(user_id, 0, 64);
    char *ssid = cJSON_GetObjectItem(json_root, "wifi_name")->valuestring;
    char *code = cJSON_GetObjectItem(json_root, "wifi_code")->valuestring;
    int len1 = strlen(ssid);
    int len2 = strlen(code);
    memcpy(user_id,ssid,strlen(ssid));
    memcpy(user_code,code,strlen(code));
    user_id[len1] = '\0';
    user_code[len2] = '\0';
    cJSON_Delete(json_root);
    //  ESP_LOGI(TAG, "json load  finished. SSID:%d password:%d ",ssid,code);
    // ESP_LOGI(TAG, "json load  finished. SSID:%s password:%s ",user_id,user_code);
    printf("\r\nwifi_ssid:");
    for(int i = 0;i<len1;i++){
        printf("%c",user_id[i]);
    }

    printf("\r\nwifi_code:");
    for(int i = 0;i<len2;i++){
        printf("%c",user_code[i]);
    }
    printf("\r\n");
    nvs_write_wifi_data((char *)"WIFI Config Is OK!", user_id, user_code);
    httpd_resp_sendstr(req, "Post control value successfully");

    vTaskDelay(pdMS_TO_TICKS(1000));
    esp_restart();
    return ESP_OK;
}

esp_err_t web_server_start(void)
{

    static struct file_server_data *server_data = NULL;

    if (server_data) {
        ESP_LOGE(TAG, "File server already started");
        return ESP_ERR_INVALID_STATE;
    }

    /* Allocate memory for server data */
    server_data = (struct file_server_data *)calloc(1, sizeof(file_server_data));
    if (!server_data) {
        ESP_LOGE(TAG, "Failed to allocate memory for server data");
        return ESP_ERR_NO_MEM;
    }

    // xTaskCreate(&webserver, "webserver_task", 2048, NULL, 5, NULL);

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    /* Use the URI wildcard matching function in order to
     * allow the same handler to respond to multiple different
     * target URIs which match the wildcard scheme */
    config.uri_match_fn = httpd_uri_match_wildcard;

    ESP_LOGI(TAG, "Starting HTTP Server on port: '%d'", config.server_port);
    if (httpd_start(&server, &config) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to start file server!");
        return ESP_FAIL;
    }

    /* URI handler for getting uploaded files */
    root.uri       = "/";  // Match all URIs of type /path/to/file
    root.method    = HTTP_GET;
    root.handler   = http_firstget_handler;
    root.user_ctx  = server_data;
    
    httpd_register_uri_handler(server, &root);

    /* URI handler for uploading files to server */
    wifi_config.uri       = "/configwifi";   // Match all URIs of type /upload/path/to/file
    wifi_config.method    = HTTP_POST;
    wifi_config.handler   = wifi_config_post_handler;
    wifi_config.user_ctx  = server_data;
    
    httpd_register_uri_handler(server, &wifi_config);
    return ESP_OK;
}